安安~我是ChiYu~
「Build 成功、測試全綠、需求完成。」
這三句,我在 AI Agent 的完成回報裡看過很多次。看起來很完整,卻沒有交代驗證的是哪一份 Code、實際跑了哪些檢查,也沒有說綠燈在哪裡停止。
昨天修正人工重送功能後,我把產品固定在完整 Commit SHA,再請 Codex GPT-5.6-SOL-HIGH 整理一份 Evidence Packet。它除了保存 Prompt、Diff、環境、命令、Exit Code 與限制,也產生一支可以重跑六項 Gate 的 PowerShell 腳本。
腳本先通過語法檢查,真正執行時卻被 dotnet --info 裡正常的空白行弄壞。修正後,我再從另一個乾淨 Worktree 直接執行同一支腳本,才得到 Restore、Build、Format、Test 與 Smoke 的完整結果:93 項測試成功、0 項失敗,重跑相同資料也沒有再次處理或通知。
這份結果仍只發生在同一台 Windows 主機。Linux、macOS、Container、真實 Provider、Coverage 與 Mutation 都沒有因此自動變成已驗證。
所以今天真正要回答的問題只有一個:
AI 說完成時,我能不能把這句話綁定到固定版本、可理解的控制流程、實際執行結果與清楚的未知範圍,讓另一個執行流程重新驗證?
如果下一位工程師不知道使用哪個 Commit、在哪種環境執行,也不知道哪些結論來自工具、哪些只是人工判讀,那句「全部通過」就像一張只有「合格」兩個字的驗車單:沒有車牌、檢查項目與日期,根本不知道它證明了什麼。
〈可重複的證明〉要求完成主張可以被重新執行與查驗。多保存幾張綠色截圖,還不足以做到這件事。
流程拆成責任清楚的單元後,Reviewer 才能逐一確認輸入、輸出、負責規則與失敗位置。Clean Code 透過小函式、清楚命名、單一責任與架構邊界,讓測試能對準真正的行為,完成宣告也能指出哪項主張由哪份證據支援。
對這個 Work Item API,我能留下的是有範圍的工程證據:單元測試、驗收測試、靜態檢查、Build、Locked Restore 與 Smoke Test。它們無法證明系統永遠正確,只能說明固定版本在明列條件下通過哪些驗證,以及哪些部分仍然未知。
我把這條因果鏈整理如下:
flowchart LR
A[說清楚完成主張] --> B[拆出可理解的責任與控制流程]
B --> C[替各項行為找到測試或驗證關卡]
C --> D[固定產品 Commit]
D --> E[記錄環境選擇規則與實際版本]
E --> F[保存命令、Exit Code 與原始輸出]
F --> G[標示人工判讀與未知範圍]
G --> H[由另一個流程重新執行]
如果只保存最後的成功摘要,規格、版本、環境與限制都會消失,下一個流程也無法重新驗證原本的完成主張。
我把今天的交付物稱為「證據封包(Evidence Packet)」。
《無瑕的程式碼 第二版》沒有規定這套文件格式。這是我把〈可重複的證明〉延伸到 AI Coding 後,整理出的工程做法:把 Prompt、產品版本、驗證命令、原始輸出、人工判讀與限制放在一起,讓 Reviewer 能從完成主張一路查回原始資料。
一份能用來 Review 的證據封包,至少要回答六類問題:
| 證據內容 | 要回答的問題 | 缺少時會發生什麼事 |
|---|---|---|
| 完成主張與輸入 | Agent 收到哪份需求、Prompt、限制與驗收條件? | 綠燈存在,卻不知道原本答應完成什麼。 |
| 產品座標 | 驗證的是哪個 Commit、Tag 與 Diff? | 結果無法綁定到確切 Code。 |
| 執行情境 | 使用哪個 SDK、Runtime、OS、套件選擇規則與外部替身? | 重新執行出現差異時,無法判斷原因。 |
| 控制流程與驗證方式 | 規格經過哪些 Controller、Use Case、Adapter 與副作用?由什麼測試或關卡支援? | 只看見測試總數,看不出保護了哪段行為。 |
| 原始結果與人工判讀 | 實際執行什麼命令、Exit Code 是多少?哪些對應是人看原始碼後做的判斷? |
Agent 摘要會和工具觀察混在一起。 |
| 限制與重跑方式 | 哪些項目沒執行、哪些環境沒驗證,下一個流程如何重跑? | 未知範圍容易被誤寫成已通過。 |
我把這個角色稱為「證據整理 Agent(Evidence Compiler)」。這裡的 Compiler 是彙整證據,不是 C# 編譯器;它只能整理既有資料,不能修改產品來換取綠燈。
本次只驗證一個已接受版本,觀察證據封包能否支援另一個流程重跑。Prompt 與 Token 不在比較範圍內。
實驗固定兩個不同的 Git 座標:
| 座標 | 用途 |
|---|---|
28758b4d9345e890e22a650d3bd7e38933982b42 |
昨天接受的產品版本,Production Code 與 Tests 都固定在這裡。 |
de20071e528e356d960f40bad62dd8e2cb590d5e |
今天保存證據封包、重跑腳本與原始輸出的發布版本。 |
驗證結果最後要綁定完整 Commit SHA;Annotated Tag 只是方便讀者找到對應位置。產品版本使用 day-25-harm-behavior-structure,證據發布版本使用 day-26-repeatable-proof。
我另外建立兩個 Worktree。一個讓證據整理 Agent 寫文件,另一個使用 Detached HEAD,直接固定在待驗證 Commit,不會跟著任何 Branch 往前移動。Agent 可以讀取產品 Worktree,不能在裡面修改 Code。
這些約束主要防止三件事:驗證對象漂移、Agent 為了綠燈修改產品,以及未執行項目被寫成通過。
| 實驗設計 | 為什麼要這樣做 |
|---|---|
| 固定完整 Commit SHA | 避免 Agent 整理證據時,驗證對象已經悄悄改變。 |
| 證據 Worktree 與產品 Worktree 分開 | 讓文件與 Log 的新增不會污染待驗證產品版本。 |
禁止修改 src、tests、Solution、套件與 Lock File |
避免 Agent 為了取得綠燈而移動功能或驗收標準。 |
| 缺少 Coverage、Mutation 或跨環境條件時必須標示未執行 | 不讓空白欄位被寫成通過。 |
| Agent 自己跑完後,由主流程再次執行 | 驗證腳本真的能離開產生它的 Session,被另一個流程使用。 |
這次的核心 Prompt 如下,完整版本也保存在公開 Repository:
你正在替公開的 Work Item API 建立一份「可重複的證明」初稿。
你的任務是整理證據,不是修改產品。
固定條件:
- 執行模型為 Codex GPT-5.6-SOL-HIGH。
- 待驗證版本必須是 Annotated Tag
day-25-harm-behavior-structure 指向的 Commit:
28758b4d9345e890e22a650d3bd7e38933982b42。
- 禁止修改 src、tests、Solution、套件版本、Lock File、
既有 Evidence 與 Repository Instruction。
- 不要 Commit、Tag、Push 或移除檔案。
你要回答:
另一位工程師只拿到固定 Commit 與這份 Evidence Packet,
能否理解完成宣告涵蓋什麼、執行相同 Gate,
並區分工具結果、人工判斷與未知範圍?
必做工作:
1. 驗證乾淨 Repository 的 HEAD、Git Status、Tag 與 Commit。
2. 盤點逾期處理與通知重試的主要控制流程。
3. 把昨天的外部行為與結構條件對應到測試或 Gate。
4. 執行固定的 Restore、Build、Format、Test 與 Smoke。
5. Coverage、Mutation、套件弱點與跨環境重現缺少條件時,
標成未執行或受限,不得寫成通過。
6. 建立 README、proof matrix、replay.ps1、limitations,
並保存每項 Gate 的完整命令、退出碼與原始輸出。
7. 說明 global.json 與 packages.lock.json 能固定到什麼程度,
不得把相容 Patch、相同套件圖與位元級重現混為一談。
完成標準:
- 每個通過宣告都能連到原始輸出。
- 每個規格都能連到可理解的程式單元與驗證方式,或明列缺口。
- 失敗、跳過、網路限制與人工判斷都可見。
- 不把測試與工具輸出描述成完整數學證明。
- Production Code 與 Tests 保持零 Diff。
證據整理 Agent 不能臨時安裝 Coverage 工具,也不能改寫測試或 Production Code。只要它改變產品或驗收方式,今天驗證的對象就不再是原本的固定 Commit。
只列出測試數量,讀者仍然不知道它們保護哪段系統。證據整理 Agent 因此先追蹤兩條 HTTP 入口。
第一條是逾期處理。Controller 呼叫 IOverdueWorkItemProcessor;Processor 先讓 Marker 判定逾期並保存 Work Item 狀態與 Outbox,再交給 Dispatcher 處理 Pending 通知。
第二條是昨天新增的人工通知重試。Retry Scheduler 會找到既有 Pending Outbox,重新排定同一筆通知意圖;外部呼叫仍統一交給 Dispatcher。
flowchart LR
A[POST process-overdue] --> B[Overdue Work Item Processor]
B --> C[Marker 判定逾期]
C --> D[(Work Item 與 Outbox 同次保存)]
D --> E[Outbox Dispatcher]
F[POST overdue-notification retry] --> G[Retry Scheduler]
G --> H[重新排定既有 Pending Outbox]
H --> E
E --> I[Notification Sender]
I --> J[Provider]
這張圖先把 HTTP 回應、逾期判定、資料保存與通知傳送分開。繼續往下追,還能找到重新排定、Lease Claim、ACK、Retry 與 Cancellation 各自的負責位置。
有了這份責任拆分,Reviewer 才能從外部行為追到負責的程式單元與測試。本次沒有準備控制流程混亂的對照版本,所以只能確認目前結構足以建立對照,不能推論它節省了多少 Token。
Agent 接著建立 Proof Matrix。為了避免把它誤解成完整數學證明,正文稱它為「證據對照矩陣」:每一列都把完成主張、負責單元、驗證方式與未知範圍排在一起。
下面是其中幾列的簡化版本:
| 完成主張 | 負責單元 | 本次證據 | 證據停止在哪裡 |
|---|---|---|---|
找不到 Work Item 時,API 回 404 Not Found |
Controller 將 Use Case 結果映射成 HTTP 回應 | 具名契約測試存在,完整測試關卡通過 | 沒有部署環境的 HTTP Trace |
沒有可重新排定的 Pending Outbox 時,API 回 409 Conflict |
Retry Scheduler 查詢通知意圖 | 契約測試存在,完整測試關卡通過 | 沒有涵蓋所有 Lease 競爭組合 |
| 相同 POST 重複執行時沿用同一筆 Outbox 與冪等鍵 | Scheduler 只更新既有通知,不新增一筆 | 重複呼叫測試存在,完整測試關卡通過 | 只驗證單機、循序 Request |
| Controller 不直接呼叫 Sender | HTTP 入口只依賴 Retry Use Case | Architecture Test 存在,完整測試關卡通過 | Source Scan 不是完整依賴圖證明 |
| 先保存狀態與 Outbox,再呼叫 Dispatcher | Processor 固定協調順序 | Boundary Test 與行為測試存在 | 沒有分散式交易驗證 |
| Lost ACK 後沿用同一把 Key 重試 | Dispatcher 與 Provider 契約 | Dispatcher 測試存在,完整測試關卡通過 | 非冪等 Provider 仍可能產生重複外部效果 |
這張表沒有把每一列都寫成「已證明」。本次 dotnet test 只保存整體摘要,沒有產生 TRX;TRX 是能記錄測試回合與逐項結果的結構化檔案。因此,具名測試與需求的對應是我閱讀原始碼後建立的人工判讀,工具只能證明整體測試命令成功。
Agent 實際執行的主要命令如下:
dotnet --info
dotnet restore .\AiCleanCode.sln --locked-mode
dotnet build .\AiCleanCode.sln --configuration Release --no-restore
dotnet format .\AiCleanCode.sln --verify-no-changes --no-restore
dotnet test .\AiCleanCode.sln --configuration Release --no-build --no-restore
.\scripts\run-series-baseline-smoke.ps1
每項驗證關卡回答的問題不同:
| 驗證關卡 | 能回答什麼 | 不能回答什麼 |
|---|---|---|
.NET 環境資訊 |
本次實際選到的 SDK、Runtime、OS 與 win-x64 執行環境識別 |
其他機器會不會取得完全相同環境 |
| Locked Restore | Lock File 與專案定義能否在鎖定模式下還原 | NuGet 服務永遠可用、所有供應鏈都安全 |
| Release Build | 固定版本能否編譯,是否出現 Warning 或 Error | 執行行為一定正確 |
| Format Verify | Repository 的格式規則是否被破壞 | 命名、責任與架構是否合理 |
| Release Test | 已寫入測試的 Assertion 是否通過 | 沒有被測試的需求與未知狀態 |
| HTTP Smoke | API 啟動後,主要成功路徑與相同資料重跑是否成立 | 所有失敗、並行、部署與真實 Provider 行為 |
證據封包會保留每項關卡的名稱、命令與涵蓋範圍,Reviewer 才知道這些綠燈分別回答了什麼。

圖:可重複證明要固定版本、保存原始輸出並列出未知;所有工具全綠仍不等於全部需求已被證明。
待驗證版本有一份 global.json 與五份 packages.lock.json,Agent 也真的使用 --locked-mode 還原套件。
Lock File 會保存 NuGet 解析後的直接與傳遞套件版本。當專案相依需求與 Lock File 不一致時,Locked Mode 應直接失敗,而不是悄悄改寫套件圖。這能降低套件解析結果在不同時間漂移的風險。
可是它仍然沒有凍結整個執行環境。
這個 Repository 的 global.json 指定 SDK 10.0.300,並設定 rollForward: latestPatch。依這項規則,.NET 會在相同 Major、Minor 與 Feature Band 中,選擇不低於指定版本的最新已安裝 Patch;找不到符合版本時就失敗。
Microsoft 文件建議,在 Package Lock File 必須與 SDK 嚴格同步時,把 rollForward 設成 disable。本 Demo 保留 latestPatch,接受同一 Feature Band 內的 Patch 更新,也就沒有要求每台機器使用完全相同的 SDK。
本次實際選到 SDK 10.0.300,Host Runtime 則是 10.0.8。SDK 與 Runtime 是兩個版本維度,OS、NuGet Cache、SQLite 原生資產與語系也可能不同。
Locked Restore 只能約束套件解析;本次沒有比較 DLL、PDB 等建置產物,因此沒有驗證位元級重現。
證據整理 Agent 寫完重跑驗證腳本(Replay)後,先做了 PowerShell 語法檢查。語法通過,看起來一切正常。
真正執行時,腳本卻停在第一項主要關卡 dotnet --info:
Cannot bind argument to parameter 'RawOutput'
because it is an empty string.
dotnet --info 的輸出本來就包含空白行。Agent 產生的 Write-GateOutput 參數不接受空字串,於是這支「用來證明其他東西沒壞」的腳本,反而先被正常輸出弄壞。
這次出錯的不是 Production Code。產品測試甚至沒有機會先報錯,因為重跑流程在進入主要驗證前就停止了。
Agent 最後替 RawOutput 加上 [AllowEmptyString()],保留 Exit Code 1 與錯誤原因,再改用新的輸出目錄重跑。修正後,六項主要關卡才完整執行成功。
另一次重跑因外層執行器只給一秒而中止,沒有留下完整結果。我把它記為執行環境中斷,不算產品失敗,也不算驗證通過;細節保留在公開 Evidence。
如果只把最後成功的 Log 放上 GitHub,讀者就不會知道這支腳本曾經無法處理正常輸出。
產品要留下證據,產生與重播證據的工具也必須真的執行過。腳本沒有實際跑過,就不能宣稱這份證據可以重複。
Agent 修好腳本並自行重跑成功後,我先 Review 腳本做了哪些事:
Exit Code 不為 0,就停止並留下已產生的輸出。我把「主流程複驗」定義為:從另一個乾淨 Worktree 直接執行 Agent 產生的腳本,並把結果保存到新的輸出目錄。
主流程結果如下:
| 驗證範圍 | 實際結果 |
|---|---|
| Repository Preflight | HEAD 與指定 Commit 相同,執行前工作區乾淨 |
| .NET 環境 | SDK 10.0.300、Host Runtime 10.0.8、Windows win-x64 |
| Locked Restore | 五個專案依 Lock File 還原成功 |
| Release Build | 零個 Warning、零個 Error |
| Format Verify | 沒有格式差異 |
| Release Test | 測試命令成功完成;原始摘要記錄 93 項成功、0 項失敗、0 項略過 |
| HTTP Smoke | 第一次處理兩筆到期資料並嘗試兩次通知;對相同資料重跑時,沒有再次處理或通知 |
93 是測試執行數量,不代表 93 種風險都已涵蓋。這份結果能支持的主張是:固定產品 Commit 在本次 Windows/.NET 環境中,可以依相同順序完成六項驗證,而且重跑腳本能被另一個執行流程使用。
它不能支持的主張包括:
Repository 沒有固定的 Coverage 與 Mutation Test 工具或命令,所以本次標記為「未執行」。我也沒有在驗證途中臨時加入套件與門檻,避免改變固定產品版本的驗收條件。
這次證據整理 Agent 的單一 Session 使用了 205,358 個 Fresh Input Token。完整的執行時間、Cached Input、Output、Reasoning 與工具呼叫次數都保留在公開 Evidence。
這不是節省 Token 的案例。
Agent 需要讀取規格、Production Code、Tests、既有 Evidence 與工具輸出,還要建立矩陣並實際驗證重跑腳本。這些工作構成主要的 Context 成本。
本次沒有安排「Reviewer 不使用證據封包」的控制組,也沒有讓兩組 Reviewer 執行相同交接任務。因此,205,358 個 Fresh Input Token 只能描述這次成本,不能推論後續一定省 Token。
目前能確認的價值比較窄:Reviewer 不必只靠一句「測試全綠」猜測完成範圍,可以直接從固定座標、控制流程、原始結果與未知範圍開始查證。
Agent 最後那段「已完成」只能算一筆回報,不能自動升級成完成證據。
A — Auditable by Evidence 實據可審 要求 User 至少分開四個層次:
| 層次 | 要問的問題 |
|---|---|
| 完成主張 | 這次究竟答應完成什麼?驗證哪個版本? |
| 工具結果 | 實際執行哪些命令?Exit Code 與原始輸出是什麼? |
| 人工判讀 | 哪些規格對應、契約與接受決策是 Reviewer 的工程判斷? |
| 未知範圍 | 哪些工具沒執行、哪些環境與失敗路徑沒有涵蓋? |
Agent 回報六項關卡通過後,我仍要核對產品 Commit、Review 重跑腳本並親自複驗。Coverage、Mutation、跨環境與真實 Provider 則保留在未知範圍。A 關心的是每份證據能支撐哪項風險,以及證據在哪裡停止。
證據封包不應變成所有修改都必須繳交的巨大表格。合理的證據重量會跟著風險改變:
| 變更情境 | 合理證據 |
|---|---|
| 修正文件錯字或標點 | 固定 Diff、Markdown 檢查與人工預覽 |
| 內部 Rename | Diff、編譯、格式、呼叫端與外部 Contract 測試 |
| 單一純函式規則 | 具名單元測試、邊界值與完整測試 |
| 資料庫結構、授權、交易或公開 API | 資料庫變更、Contract/整合測試、錯誤路徑與回復方式 |
| 通知、付款或其他外部副作用 | 重送、冪等、Lost ACK、持久化意圖、Smoke 與人工契約決策 |
| 發布與跨環境交付 | CI 產物、環境資訊、部署紀錄、健康檢查與 UAT |
Work Item API 涉及 Outbox、人工重送與外部通知,所以需要保存控制流程、重跑腳本與限制矩陣。修改 Markdown 標點時,只要固定 Diff、執行 Markdown 檢查並人工預覽即可。
完整 Prompt、去識別化後的 Agent Session、證據對照矩陣、失敗紀錄、重跑腳本與主流程輸出,都已放進公開的 API Demo Repository。
讀者可以先切到 Evidence Tag 取得證據封包,再建立另一個 Worktree 固定產品版本:
git clone https://github.com/eric861129/AI-CleanCode-API-Demo.git
cd AI-CleanCode-API-Demo
git fetch --tags
git switch --detach day-26-repeatable-proof
git worktree add --detach ..\day-26-clean-replay 28758b4d9345e890e22a650d3bd7e38933982b42
.\docs\evidence\day-26\repeatable-proof\agent-draft\replay.ps1 `
-RepositoryPath '..\day-26-clean-replay' `
-ExpectedCommit '28758b4d9345e890e22a650d3bd7e38933982b42'
這些指令提供另一位工程師重跑的入口,但本次還沒有觀察到另一位工程師或另一台主機完成驗證。讀者執行時仍要使用符合 global.json 選擇規則的環境,並確認相同命令與明列行為條件成立;路徑、耗時、GUID、Port、語系與 Log 文字本來就可能不同。
回到標題,AI 說「測試全綠」還不夠,因為這句話若沒有固定 Commit、環境、命令、Exit Code 與原始輸出,就無法確認它究竟描述哪一次執行。即使這些都補齊,也還要把人工判讀與未知範圍分開。
這次真正成立的,是固定產品 Commit 能在本次 Windows/.NET 環境中,由另一個乾淨 Worktree 依相同腳本重跑六項 Gate。它沒有證明跨平台、真實 Provider、Coverage、Mutation 或 Production Deployment。
證據工具本身也要接受驗證。Replay 腳本通過語法檢查,仍被正常空白行弄壞;保留失敗、修正後再重新執行,才確認它真的能用。A — Auditable by Evidence 實據可審 要求 User 固定產品版本,分清楚工具結果、人工判讀與未知項目,再決定這份證據是否足以承擔本次風險。
今天把交付後的完成結果做成可重跑證據;明天,我會把驗證時間往前移,看看一次累積大批修改,和多個能快速整合、驗證與回復的小週期,會帶來什麼差別。